/*
 * Copyright (C) 2014 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okio;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.concurrent.TimeUnit;

/**
 * A policy on how much time to spend on a task before giving up. When a task
 * times out, it is left in an unspecified state and should be abandoned. For
 * example, if reading from a source times out, that source should be closed and
 * the read should be retried later. If writing to a sink times out, the same
 * rules apply: close the sink and retry later.
 *
 * <h3>Timeouts and Deadlines</h3> This class offers two complementary controls
 * to define a timeout policy.
 *
 * <p>
 * <strong>Timeouts</strong> specify the maximum time to wait for a single
 * operation to complete. Timeouts are typically used to detect problems like
 * network partitions. For example, if a remote peer doesn't return <i>any</i>
 * data for ten seconds, we may assume that the peer is unavailable.
 *
 * <p>
 * <strong>Deadlines</strong> specify the maximum time to spend on a job,
 * composed of one or more operations. Use deadlines to set an upper bound on
 * the time invested on a job. For example, a battery-conscious app may limit
 * how much time it spends pre-loading content.
 */
public class Timeout {
	/**
	 * An empty timeout that neither tracks nor detects timeouts. Use this when
	 * timeouts aren't necessary, such as in implementations whose operations do not
	 * block.
	 */
	public static final Timeout NONE = new Timeout() {
		@Override
		public Timeout timeout(long timeout, TimeUnit unit) {
			return this;
		}

		@Override
		public Timeout deadlineNanoTime(long deadlineNanoTime) {
			return this;
		}

		@Override
		public void throwIfReached() throws IOException {
		}
	};

	/**
	 * True if {@code deadlineNanoTime} is defined. There is no equivalent to null
	 * or 0 for {@link System#nanoTime}.
	 */
	private boolean hasDeadline;
	private long deadlineNanoTime;
	private long timeoutNanos;

	public Timeout() {
	}

	/**
	 * Wait at most {@code timeout} time before aborting an operation. Using a
	 * per-operation timeout means that as long as forward progress is being made,
	 * no sequence of operations will fail.
	 *
	 * <p>
	 * If {@code timeout == 0}, operations will run indefinitely. (Operating system
	 * timeouts may still apply.)
	 */
	// 在终止操作之前，最多等待{@code超时}时间。使用每个操作的超时意味着只要进行了前进的过程，就不会有操作序列失败。
	// 如果{@code timeout == 0}，操作将无限期运行。(操作系统超时可能仍然适用。)
	public Timeout timeout(long timeout, TimeUnit unit) {
		if (timeout < 0)
			throw new IllegalArgumentException("timeout < 0: " + timeout);
		if (unit == null)
			throw new IllegalArgumentException("unit == null");
		this.timeoutNanos = unit.toNanos(timeout);
		return this;
	}

	/** Returns the timeout in nanoseconds, or {@code 0} for no timeout. */
	public long timeoutNanos() {
		return timeoutNanos;
	}

	/** Returns true if a deadline is enabled. */
	public boolean hasDeadline() {
		return hasDeadline;
	}

	/**
	 * Returns the {@linkplain System#nanoTime() nano time} when the deadline will
	 * be reached.
	 *
	 * @throws IllegalStateException if no deadline is set.
	 */
	public long deadlineNanoTime() {
		if (!hasDeadline)
			throw new IllegalStateException("No deadline");
		return deadlineNanoTime;
	}

	/**
	 * Sets the {@linkplain System#nanoTime() nano time} when the deadline will be
	 * reached. All operations must complete before this time. Use a deadline to set
	 * a maximum bound on the time spent on a sequence of operations.
	 */
	public Timeout deadlineNanoTime(long deadlineNanoTime) {
		this.hasDeadline = true;
		this.deadlineNanoTime = deadlineNanoTime;
		return this;
	}

	/** Set a deadline of now plus {@code duration} time. */
	public final Timeout deadline(long duration, TimeUnit unit) {
		if (duration <= 0)
			throw new IllegalArgumentException("duration <= 0: " + duration);
		if (unit == null)
			throw new IllegalArgumentException("unit == null");
		return deadlineNanoTime(System.nanoTime() + unit.toNanos(duration));
	}

	/** Clears the timeout. Operating system timeouts may still apply. */
	public Timeout clearTimeout() {
		this.timeoutNanos = 0;
		return this;
	}

	/** Clears the deadline. */
	public Timeout clearDeadline() {
		this.hasDeadline = false;
		return this;
	}

	/**
	 * Throws an {@link InterruptedIOException} if the deadline has been reached or
	 * if the current thread has been interrupted. This method doesn't detect
	 * timeouts; that should be implemented to asynchronously abort an in-progress
	 * operation.
	 */
	public void throwIfReached() throws IOException {
		if (Thread.interrupted()) {
			throw new InterruptedIOException("thread interrupted");
		}

		if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
			throw new InterruptedIOException("deadline reached");
		}
	}

	/**
	 * Waits on {@code monitor} until it is notified. Throws
	 * {@link InterruptedIOException} if either the thread is interrupted or if this
	 * timeout elapses before {@code monitor} is notified. The caller must be
	 * synchronized on {@code monitor}.
	 *
	 * <p>
	 * Here's a sample class that uses {@code waitUntilNotified()} to await a
	 * specific state. Note that the call is made within a loop to avoid unnecessary
	 * waiting and to mitigate spurious notifications.
	 *
	 * <pre>
	 * {
	 * 	&#64;code
	 *
	 * 	class Dice {
	 * 		Random random = new Random();
	 * 		int latestTotal;
	 *
	 * 		public synchronized void roll() {
	 * 			latestTotal = 2 + random.nextInt(6) + random.nextInt(6);
	 * 			System.out.println("Rolled " + latestTotal);
	 * 			notifyAll();
	 * 		}
	 *
	 * 		public void rollAtFixedRate(int period, TimeUnit timeUnit) {
	 * 			Executors.newScheduledThreadPool(0).scheduleAtFixedRate(new Runnable() {
	 * 				public void run() {
	 * 					roll();
	 * 				}
	 * 			}, 0, period, timeUnit);
	 * 		}
	 *
	 * 		public synchronized void awaitTotal(Timeout timeout, int total) throws InterruptedIOException {
	 * 			while (latestTotal != total) {
	 * 				timeout.waitUntilNotified(this);
	 * 			}
	 * 		}
	 * 	}
	 * }
	 * </pre>
	 */
	public final void waitUntilNotified(Object monitor) throws InterruptedIOException {
		try {
			boolean hasDeadline = hasDeadline();
			long timeoutNanos = timeoutNanos();

			if (!hasDeadline && timeoutNanos == 0L) {
				monitor.wait(); // There is no timeout: wait forever.
				return;
			}

			// Compute how long we'll wait.
			long waitNanos;
			long start = System.nanoTime();
			if (hasDeadline && timeoutNanos != 0) {
				long deadlineNanos = deadlineNanoTime() - start;
				waitNanos = Math.min(timeoutNanos, deadlineNanos);
			} else if (hasDeadline) {
				waitNanos = deadlineNanoTime() - start;
			} else {
				waitNanos = timeoutNanos;
			}

			// Attempt to wait that long. This will break out early if the monitor is
			// notified.
			long elapsedNanos = 0L;
			if (waitNanos > 0L) {
				long waitMillis = waitNanos / 1000000L;
				monitor.wait(waitMillis, (int) (waitNanos - waitMillis * 1000000L));
				elapsedNanos = System.nanoTime() - start;
			}

			// Throw if the timeout elapsed before the monitor was notified.
			if (elapsedNanos >= waitNanos) {
				throw new InterruptedIOException("timeout");
			}
		} catch (InterruptedException e) {
			throw new InterruptedIOException("interrupted");
		}
	}
}
